import { G6 } from "@/g6/g6";

const isString = G6.Util.isString;
const deepMix = G6.Util.deepMix;
const { delegateStyle } = require("../utils/styles");
const body = document.body;

const ALLOW_EVENTS = ["shift"];

export function dragNode(G6) {
  G6.registerBehavior("drag-node", {
    getDefaultCfg() {
      return {
        updateEdge: true,
        delegateStyle: {},
        // 是否开启delegate
        enableDelegate: false,

        ondragend: () => undefined,
      };
    },
    getEvents() {
      return {
        "node:dragstart": "onDragStart",
        "node:drag": "onDrag",
        "node:dragend": "onDragEnd",
        "canvas:mouseleave": "onOutOfRange",

        keyup: "onKeyUp",
        keydown: "onKeyDown",
      };
    },
    onDragStart(e) {
      if (!this.shouldBegin.call(this, e)) {
        return;
      }

      const { item, target } = e;
      const hasLocked = item.hasLocked();
      if (hasLocked) {
        return;
      }

      // 如果拖动的target 是linkPoints / anchorPoints 则不允许拖动
      if (target) {
        const isAnchorPoint = target.get("isAnchorPoint");
        if (isAnchorPoint) {
          return;
        }
      }

      const graph = this.graph;

      this.targets = [];

      // 获取所有选中的元素
      const nodes = graph.findAllByState("node", "selected");

      const currentNodeId = item.get("id");

      // 当前拖动的节点是否是选中的节点
      const dragNodes = nodes.filter((node) => {
        const nodeId = node.get("id");
        return currentNodeId === nodeId;
      });

      // 只拖动当前节点
      if (dragNodes.length === 0) {
        this.target = item;
        this.target.origin = {
          x: item._cfg.model.x,
          y: item._cfg.model.y,
        };
      } else {
        // 拖动多个节点
        if (nodes.length > 1) {
          nodes.forEach((node) => {
            const hasLocked = node.hasLocked();
            if (!hasLocked) {
              node.origin = {
                x: node._cfg.model.x,
                y: node._cfg.model.y,
              };
              this.targets.push(node);
            }
          });
        } else {
          this.targets.push(item);
        }
      }

      this.origin = {
        x: e.x,
        y: e.y,
      };

      this.point = {};
      this.originPoint = {};

      graph.emit("drag-node:start");
    },
    onDrag(e) {
      if (!this.origin) {
        return;
      }
      if (!this.get("shouldUpdate").call(this, e)) {
        return;
      }
      const graph = this.graph;
      const autoPaint = graph.get("autoPaint");
      graph.setAutoPaint(false);

      // 当targets中元素时，则说明拖动的是多个选中的元素
      if (this.targets.length > 0) {
        if (this.enableDelegate) {
          this._updateDelegate(e);
        } else {
          this.targets.forEach((target) => {
            this._update(target, e, this.enableDelegate);
          });
        }
      } else {
        // 只拖动单个元素
        this._update(this.target, e, this.enableDelegate);
      }

      graph.paint();
      graph.setAutoPaint(autoPaint);
    },
    onDragEnd(e) {
      if (!this.origin || !this.shouldEnd.call(this, e)) {
        return;
      }

      const graph = this.graph;
      const autoPaint = graph.get("autoPaint");
      graph.setAutoPaint(false);

      if (this.shape) {
        this.shape.remove();
        this.shape = null;
      }

      if (this.target) {
        const delegateShape = this.target.get("delegateShape");
        if (delegateShape) {
          delegateShape.remove();
          this.target.set("delegateShape", null);
        }
      }

      if (this.targets.length > 0) {
        // 获取所有已经选中的节点
        this.targets.forEach((node) => this._update(node, e));
      } else if (this.target) {
        this._update(this.target, e);
      }

      this.point = {};
      this.origin = null;
      this.originPoint = {};
      this.targets.length = 0;
      this.target = null;
      // 终止时需要判断此时是否在监听画布外的 mouseup 事件，若有则解绑
      const fn = this.fn;
      if (fn) {
        body.removeEventListener("mouseup", fn, false);
        this.fn = null;
      }

      graph.paint();
      graph.setAutoPaint(autoPaint);
      graph.emit("drag-node:end");
    },
    // 若在拖拽时，鼠标移出画布区域，此时放开鼠标无法终止 drag 行为。在画布外监听 mouseup 事件，放开则终止
    onOutOfRange(e) {
      const self = this;
      if (this.origin) {
        const canvasElement = self.graph.get("canvas").get("el");
        const fn = (ev) => {
          if (ev.target !== canvasElement) {
            self.onDragEnd(e);
          }
        };
        this.fn = fn;
        body.addEventListener("mouseup", fn, false);
      }
    },
    _update(item, e, force) {
      const origin = this.origin;
      const model = item.get("model");
      const nodeId = item.get("id");
      if (!this.point[nodeId]) {
        this.point[nodeId] = {
          x: model.x,
          y: model.y,
        };
      }

      /*---------------------------------------自动调节位置-------------------------------------------*/
      let durx = e.x - origin.x;
      let dury = e.y - origin.y;
      const grid = 5;

      if (durx % grid > grid / 2) {
        durx = Math.ceil(durx / grid) * grid;
      } else {
        durx = Math.floor(durx / grid) * grid;
      }
      if (dury % grid > grid / 2) {
        dury = Math.ceil(dury / grid) * grid;
      } else {
        dury = Math.floor(dury / grid) * grid;
      }

      if (this.keydown) {
        if (Math.abs(durx) > Math.abs(dury)) {
          dury = 0;
        } else {
          durx = 0;
        }
      }

      if (!item.origin) {
        return;
      }

      let x = durx + item.origin.x;
      let y = dury + item.origin.y;

      /*---------------------------------------自动调节位置 end-------------------------------------------*/

      // 拖动单个未选中元素
      if (force) {
        this._updateDelegate(e, x, y);
        return;
      }

      const pos = { x, y };

      if (this.get("updateEdge")) {
        this.graph.updateItem(item, pos);
      } else {
        item.updatePosition(pos);
        // this.graph.paint();
      }
    },
    /**
     * 更新拖动元素时的delegate
     * @param {Event} e 事件句柄
     * @param {number} x 拖动单个元素时候的x坐标
     * @param {number} y 拖动单个元素时候的y坐标
     */
    _updateDelegate(e, x, y) {
      const bbox = e.item.get("keyShape").getBBox();
      if (!this.shape) {
        // 拖动多个
        const parent = this.graph.get("group");
        const attrs = deepMix({}, delegateStyle, this.delegateStyle);
        if (this.targets.length > 0) {
          const { x, y, width, height, minX, minY } = this.calculationGroupPosition();
          this.originPoint = { x, y, width, height, minX, minY };
          // model上的x, y是相对于图形中心的，delegateShape是g实例，x,y是绝对坐标
          this.shape = parent.addShape("rect", {
            attrs: {
              width,
              height,
              x,
              y,
              ...attrs,
            },
          });
        } else if (this.target) {
          this.shape = parent.addShape("rect", {
            attrs: {
              width: bbox.width,
              height: bbox.height,
              x: x + bbox.x,
              y: y + bbox.y,
              ...attrs,
            },
          });
          this.target.set("delegateShape", this.shape);
        }
        this.shape.set("capture", false);
      } else {
        if (this.targets.length > 0) {
          const clientX = e.x - this.origin.x + this.originPoint.minX;
          const clientY = e.y - this.origin.y + this.originPoint.minY;
          this.shape.attr({
            x: clientX,
            y: clientY,
          });
        } else if (this.target) {
          this.shape.attr({
            x: x + bbox.x,
            y: y + bbox.y,
          });
        }
      }

      // this.graph.paint();
    },
    /**
     * 计算delegate位置，包括左上角左边及宽度和高度
     * @memberof ItemGroup
     * @return {object} 计算出来的delegate坐标信息及宽高
     */
    calculationGroupPosition() {
      const graph = this.graph;

      const nodes = graph.findAllByState("node", "selected");

      let minx = Infinity;
      let maxx = -Infinity;
      let miny = Infinity;
      let maxy = -Infinity;

      // 获取已节点的所有最大最小x y值
      for (const id of nodes) {
        const element = isString(id) ? graph.findById(id) : id;
        const bbox = element.getBBox();
        const { minX, minY, maxX, maxY } = bbox;
        if (minX < minx) {
          minx = minX;
        }

        if (minY < miny) {
          miny = minY;
        }

        if (maxX > maxx) {
          maxx = maxX;
        }

        if (maxY > maxy) {
          maxy = maxY;
        }
      }
      const x = Math.floor(minx) - 20;
      const y = Math.floor(miny) + 10;
      const width = Math.ceil(maxx) - x;
      const height = Math.ceil(maxy) - y;

      return {
        x,
        y,
        width,
        height,
        minX: minx,
        minY: miny,
      };
    },

    onKeyDown(e) {
      const code = e.key;
      if (ALLOW_EVENTS.indexOf(code.toLowerCase()) > -1) {
        this.keydown = true;
      } else {
        this.keydown = false;
      }
    },
    onKeyUp() {
      this.keydown = false;
    },
  });
}
